package com.wenlin.userservice.lock;

import com.wenlin.userservice.exception.NotFoundException;
import com.wenlin.userservice.response.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import redis.clients.jedis.JedisCommands;

import java.util.concurrent.TimeUnit;

/***
 *  Created with IntelliJ IDEA.
 *  Author:  wenlin
 *  Date:  2018/3/26 14:01
 *  Description:redis锁
 **/
@Service
public class RedisService {

    private static Logger logger = LoggerFactory.getLogger(RedisService.class);
    //redis锁setnx键
    public static String LOCK_NX = "NX_TEST_LOCK";

    //redis锁setnx键
    public static String LOCK_EX = "EX_TEST_LOCK";


    private static final String LOCKED = "redis setNx 测试";
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /***
     *  Author:  wenlin
     *  Date:  2018/3/26
     *  Description:手动抛出自定义异常
     **/
    private void exception() {
        throw new NotFoundException();
    }

    //reids setEx方式加锁
    public Message setRedisEx() {
        redisTemplate.getConnectionFactory().getConnection()
                .setEx(LOCK_EX.getBytes(), 10, LOCKED.getBytes());
        Boolean isKey = false;
        return new Message(isKey);
    }
    //从 Redis 2.6.12 版本开始， SET 命令的行为可以通过一系列参数来修改：
    //EX second ：设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
    //PX millisecond ：设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
    //NX ：只在键不存在时，才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
    //XX ：只在键已经存在时，才对键进行设置操作。
    /***
     *  Author:  wenlin
     *  Date:  2018/3/26
     *  Description:redis setNx示例
     **/
    public Message redisExcuteNx() {
        Boolean flag = redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            //RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            boolean flagNX = redisConnection.setNX(LOCK_NX.getBytes(), LOCKED.getBytes());
            exception();
            //加锁和加过期时间分为2步，无法保证原子性，如果在期间发生异常，则导致锁无过期时间，导致业务故障
            redisConnection.expire(LOCK_NX.getBytes(),20);
            return flagNX;
        });
        return new Message(flag);
    }
    /***
     *  Author:  wenlin
     *  Date:  2018/3/26
     *  Description:setEx示例
     **/
    public Message redisExcuteEx() {
        String results = redisTemplate.execute((RedisCallback<String>) redisConnection -> {
            //RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
            JedisCommands commands = (JedisCommands) redisConnection.getNativeConnection();
            //Nx,不存在，set PX 设置过期时间
            String result = commands.set(LOCK_EX,"获取的资源","NX","PX",10000);
            logger.info(result);
            return result;
        });
        if(StringUtils.isEmpty(results)){
            results = "分布锁已存在,请等待";
            return new Message(results);
        }
        if(results.equals("OK"))
        {
            results = "获取分布式成功，执行业务";
        }
        else{
            results = "分布锁已存在,请等待";
        }
        return new Message(results);
    }

    //存在问题

    /*线程T1获取锁
    线程T1执行业务操作，由于某些原因阻塞了较长时间
    锁自动过期，即锁自动释放了
    线程T2获取锁
    线程T1业务操作完毕，释放锁（其实是释放的线程T2的锁）*/


}

